package com.yxw.bamboo.core.database.mssql;

import org.springframework.stereotype.Component;

import java.nio.ByteBuffer;

@Component
public class MsBufferWrite {

    void writeInt(int value, ByteBuffer stagingBuffer)  {
        stagingBuffer.putInt(value);
    }


    void writeChar(char value,ByteBuffer stagingBuffer){
        stagingBuffer.putChar(value);
    }

    void writeShort(short value,ByteBuffer stagingBuffer) {
        stagingBuffer.putShort(value);
    }

    void writeString(String value,ByteBuffer stagingBuffer) throws Exception {
        byte[] valueBytes=new byte[value.length()*2];
        int charsCopied = 0;
        int length = value.length();
        while (charsCopied < length) {
            int bytesToCopy = 2 * (length - charsCopied);

            int bytesCopied = 0;
            try {
                while (bytesCopied < bytesToCopy) {
                    char ch = value.charAt(charsCopied++);
                    valueBytes[bytesCopied++] = (byte) ((ch >> 0) & 0xFF);
                    valueBytes[bytesCopied++] = (byte) ((ch >> 8) & 0xFF);
                }

                writeBytes(valueBytes, 0, bytesCopied,stagingBuffer);
            } catch (ArrayIndexOutOfBoundsException e) {
                throw  new Exception("copy fail");
            }
        }
    }

    public void writeBytes(byte[] value,ByteBuffer stagingBuffer)  {
        writeBytes(value, 0, value.length,stagingBuffer);
    }

    void writeBytes(byte[] value, int offset, int length,ByteBuffer stagingBuffer)  {
        assert length <= value.length;

        int bytesWritten = 0;
        int bytesToWrite;

        while ((bytesToWrite = length - bytesWritten) > 0) {

            if (bytesToWrite > stagingBuffer.remaining())
                bytesToWrite = stagingBuffer.remaining();

            stagingBuffer.put(value, offset + bytesWritten, bytesToWrite);

            bytesWritten += bytesToWrite;
        }
    }
    /**
     * Writes a string value as Unicode for RPC
     *
     * @param sName
     *        the optional parameter name
     * @param sValue
     *        the data value
     * @param bOut
     * */
   public void writeRPCStringUnicode(String sName, String sValue, boolean bOut,
                               byte[] collationBytes,ByteBuffer bf,byte cryptoMeta) throws Exception {
        boolean bValueNull = (sValue == null);
        int nValueLen = bValueNull ? 0 : (2 * sValue.length());
        boolean isShortValue = nValueLen <= 8000;


        // Use PLP encoding on Yukon and later with long values and OUT parameters
        boolean usePLP = (!isShortValue || bOut);
        if (usePLP) {
            writeRPCNameValType(sName, bOut, TDSType.NVARCHAR,bf, cryptoMeta);

            // Handle Yukon v*max type header here.
            writeVMaxHeader(nValueLen, // Length
                    bValueNull, // Is null?
                    collationBytes,bf);

            // Send the data.
            if (!bValueNull) {
                if (nValueLen > 0) {
                    writeInt(nValueLen,bf);
                    writeString(sValue,bf);
                }

                // Send the terminator PLP chunk.
                writeInt(0,bf);
            }
        } else // non-PLP type
        {
            // Write maximum length of data
            if (isShortValue) {
                writeRPCNameValType(sName, bOut, TDSType.NVARCHAR,bf,cryptoMeta);
                writeShort((short) 8000,bf);
            } else {
                writeRPCNameValType(sName, bOut, TDSType.NTEXT,bf, cryptoMeta);
                writeInt(2147483647,bf);
            }

            writeBytes(collationBytes,bf);

            // Data and length
            if (bValueNull) {
                writeShort((short) -1,bf); // actual len
            } else {
                // Write actual length of data
                if (isShortValue)
                    writeShort((short) nValueLen,bf);
                else
                    writeInt(nValueLen,bf);

                // If length is zero, we're done.
                if (0 != nValueLen)
                    writeString(sValue,bf); // data
            }
        }
    }

    /**
     * Appends a standard v*max header for RPC parameter transmission.
     *
     * @param headerLength
     *        the total length of the PLP data block.
     * @param isNull
     *        The SQL collation associated with the value that follows the v*max header. Null for non-textual types.
     */
    void writeVMaxHeader(long headerLength, boolean isNull, byte[] collationBytes,ByteBuffer bf) {
        // Send v*max length indicator 0xFFFF.
        writeShort((short) 0xFFFF,bf);

        // Send collation if requested.
        if (collationBytes!=null)
            writeBytes(collationBytes,bf);

        // Handle null here and return, we're done here if it's null.
        if (isNull) {
            // Null header for v*max types is 0xFFFFFFFFFFFFFFFF.
            writeLong(0xFFFFFFFFFFFFFFFFL,bf);
        } else if (-1 == headerLength) {
            // Append v*max length.
            // UNKNOWN_PLP_LEN is 0xFFFFFFFFFFFFFFFE
            writeLong(0xFFFFFFFFFFFFFFFEL,bf);

            // NOTE: Don't send the first chunk length, this will be calculated by caller.
        } else {
            // For v*max types with known length, length is <totallength8><chunklength4>
            // We're sending same total length as chunk length (as we're sending 1 chunk).
            writeLong(headerLength,bf);
        }
    }


    void writeLong(long value,ByteBuffer stagingBuffer)  {
        stagingBuffer.putLong(value);
    }

    // Primitive write operations

    void writeByte(byte value,ByteBuffer stagingBuffer)  {
        stagingBuffer.put(value);
    }

    // Composite write operations

    /**
     * Write out elements common to all RPC values.
     *
     * @param sName
     *        the optional parameter name
     * @param bOut
     *        boolean true if the value that follows is being registered as an output parameter
     * @param tdsType
     *        TDS type of the value that follows
     */
    void writeRPCNameValType(String sName, boolean bOut, TDSType tdsType,ByteBuffer bf,byte cryptoMeta) throws Exception {
        int nNameLen = 0;

        if (null != sName)
            nNameLen = sName.length() + 1; // The @ prefix is required for the param

        writeByte((byte) nNameLen,bf); // param name len
        if (nNameLen > 0) {
            writeChar('@',bf);
            writeString(sName,bf);
        }

        writeByte(cryptoMeta,bf); // status // status
        writeByte(tdsType.byteValue(),bf); // type
    }
}
